#include "index-tree.h"

#include <fstream>
#include <sstream>
#include <list>

#include "util/logger/logger.h"
#include "util/strutil.h"


namespace
{

std::string makeTitle(const fs::path &dir) {
    std::ostringstream ss;
    ss << "IndexTree(" << fs::absolute(dir).lexically_normal().generic_string() << ") ";
    return ss.str();
}

} // namespace anonymous

namespace book
{

IndexTree::IndexTree()
    : title_("IndexTree() ") {
}

IndexTree::IndexTree(const fs::path &dir)
    : title_(makeTitle(dir))
    , rootDir_(fs::absolute(dir).lexically_normal()) {

    if (!fs::is_directory(dir)) {
        std::ostringstream ss;
        ss << title() << "constructor param should be directory, param: " << dir;
        throw NotDirError(ss.str().c_str());
    }

    buildDir(&root_, dir);

    clue();
}

IndexTree::~IndexTree() {
}

fs::path IndexTree::conv(const std::string_view &uriPath) {
    return fs::path();
}

std::vector<IndexItem> IndexTree::parseIndexFile(const fs::path &indexFile) const {
    const std::string path = indexFile.generic_string();

    LOGD << title() << "parse index file: [" << path << "]";

    std::ifstream ifs(path);
    if (!ifs) {
        LOGE << title() << "cannot open index file: [" << path << "]";
        return std::vector<IndexItem>();
    }

    std::vector<IndexItem> items;

    std::string line;

    while (std::getline(ifs, line)) {
        namespace s = strutil;

        s::removeLineEnd(line);
        s::removeLineComment(line, '#');

        // line format: "dirEntryName|uriName|title"
        const s::strviews parts = s::split(s::trim(line), '|');
        if (parts.size() != 3) {
            LOGE << title() << "parsing index file [" << path << "] bad index item format: ["
                << line << "]";
            continue;
        }

        IndexItem item;
        item.setDirEntryName(s::trim(parts[0]));
        item.setUriName(s::trim(parts[1]));
        item.setTitle(s::trim(parts[2]));

        LOGD << title() << "parsing index file [" << path << "] index item found: ["
            << item.dirEntryName() << ","
            << item.uriName() << ","
            << item.title() << "]";

        items.push_back(item);
    }

    return items;
}

void IndexTree::clue() {
    ClueHelper helper(root_);
    helper.clue();
}

void IndexTree::buildDir(Node *dirNode, const fs::path &dir) const {
    const fs::path indexFile = dir / "index.txt";
    if (!fs::exists(indexFile)) {
        return;
    }

    std::vector<IndexItem> items = parseIndexFile(indexFile);
    if (items.empty()) {
        return;
    }

    for (IndexItem &item : items) {

        NewNode node;
        node->setData(std::move(item));

        const fs::path itemFsPath = dir / node->data().dirEntryName();
        const bool isDir = fs::is_directory(itemFsPath);

        node->data().setIsDir(isDir);

        if (isDir) {
            buildDir(node.get(), itemFsPath);
        }

        dirNode->addChildNode(node.take());
    }
}

std::string IndexTree::Node::fsPath() const {
    std::list<std::string> parts;
    parts.push_back(data_.dirEntryName());
    for (const Node *p = parent_; p; p = p->parent_) {
        if (p->isRoot()) {
            break;
        }
        parts.push_front(p->data().dirEntryName());
    }
    std::ostringstream ss;
    bool first = true;
    for (const std::string &part : parts) {
        if (!first) {
            ss << '/';
        }
        ss << part;
        first = false;
    }
    return ss.str();
}

std::string IndexTree::Node::uriPath() const {
    std::list<std::string> parts;
    parts.push_front(data_.uriName());
    for (const Node *p = parent_; p; p = p->parent_) {
        if (p->isRoot()) {
            break;
        }
        parts.push_front(p->data().uriName());
    }
    std::ostringstream ss;
    bool first = true;
    for (const std::string &part : parts) {
        if (!first) {
            ss << '/';
        }
        ss << part;
        first = false;
    }
    return ss.str();
}

const IndexTree::Node *IndexTree::Node::prevArticle() const {
    for (const Node *p = prev_; p; p = p->prev_) {
        if (p->data().isArticle()) {
            return p;
        }
    }
    return nullptr;
}

const IndexTree::Node *IndexTree::Node::nextArticle() const {
    for (const Node *p = next_; p; p = p->next_) {
        if (p->data().isArticle()) {
            return p;
        }
    }
    return nullptr;
}

IndexTree::Node::~Node() {
    for (Node *child : children_) {
        delete child;
    }
}

void IndexTree::Node::addChildNode(Node *node) {
    if (node) {
        node->parent_ = this;
        children_.push_back(node);
        dirEntryNameToChild_[node->data().dirEntryName()] = node;
        uriNameToChild_[node->data().uriName()] = node;
    }
}

void IndexTree::ClueHelper::clue(Node *node) {
    if (node) {
        if (prev_) {
            prev_->next_ = node;
            node->prev_ = prev_;
        }
        prev_ = node;
        for (Node *child : node->children_) {
            clue(child);
        }
    }
}

const IndexTree::Node *IndexTree::findNodeByUri(const std::string &uri) const {
    namespace s = strutil;
    s::strviews parts = s::split(uri, '/');
    const Node *cur = &root_;
    bool meetValidNode = false;
    for (const std::string_view &part : parts) {
        const Node *child = cur->findChildByUriName(part);
        if (!child) {
            return nullptr;
        }
        cur = child;
        meetValidNode = true;
    }
    if (meetValidNode) {
        return cur;
    } else {
        return nullptr;
    }
}

const IndexTree::Node *IndexTree::findClosetExistingNodeByUri(const std::string &uri) const {
    namespace s = strutil;
    s::strviews parts = s::split(uri, '/');
    const Node *cur = &root_;
    bool meetValidNode = false;
    for (const std::string_view &part : parts) {
        const Node *child = cur->findChildByUriName(part);
        if (!child) {
            break;
        }
        cur = child;
        meetValidNode = true;
    }
    if (meetValidNode) {
        return cur;
    } else {
        return nullptr;
    }
}

} // namespace book
